#pragma once
#include <iostream>
#include <functional>
using std::cout;
using std::endl;
using std::function;

namespace xy {
	template<class T>
	class auto_ptr {
	public:
		auto_ptr(T* ptr) 
			:_ptr(ptr)
		{
		}

		auto_ptr(auto_ptr& a)
			:_ptr(a._ptr)
		{
			a._ptr = nullptr;
		}

		auto_ptr& operator=(auto_ptr& a) {
			_ptr = a._ptr;
			a._ptr = nullptr;
			return *this;
		}

		~auto_ptr() {;
			delete _ptr;
			_ptr = nullptr;
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
	};

	template<class T>
	class unique_ptr {
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{
		}

		unique_ptr(unique_ptr& a) = delete;

		unique_ptr& operator=(const unique_ptr& a) = delete;

		~unique_ptr() {
			delete _ptr;
			_ptr = nullptr;
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
	};

	template<class T>
	class shared_ptr {
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _cout(new size_t(1))
		{
		}

		template<class D>
		shared_ptr(T* ptr, D del)
			:_ptr(ptr)
			,_cout(new size_t(1))
			,_del(del)
		{
		}

		shared_ptr(shared_ptr& a)
			:_ptr(a._ptr)
			,_cout(a._cout)
		{
			(*_cout)++;
		}

		shared_ptr& operator=(const shared_ptr& a) {
			if (_ptr == a._ptr) {
				return *this;
			}

			if (--(*_cout) == 0) {
				_del(_ptr);
				delete _cout;
			}

			_ptr = a._ptr;
			_cout = a._cout;
			(*_cout)++;
			return *this;
		}

		T* get() const {
			return _ptr;
		}

		~shared_ptr() {
			if (--(*_cout) == 0) {
				_del(_ptr);
				_ptr = nullptr;
				delete _cout;
			}
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
		size_t* _cout;
		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};

	template<class T>
	class weak_ptr {
	public: 
		weak_ptr(T* p)
			:_ptr(p)
		{
		}

		weak_ptr(shared_ptr<T> ptr)
			:_ptr(ptr.get())
		{
		}

		weak_ptr(const shared_ptr<T>& a)
			:_ptr(a.get())
		{
		}

		weak_ptr& operator=(const shared_ptr<T>& a) {
			_ptr = a.get();
			return *this;
		}


		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
	};
}